Skip to content

Chore/merge upstream 4.11.0#215

Merged
gabrieljablonski merged 99 commits intomainfrom
chore/merge-upstream-4.11.0
Feb 18, 2026
Merged

Chore/merge upstream 4.11.0#215
gabrieljablonski merged 99 commits intomainfrom
chore/merge-upstream-4.11.0

Conversation

@gabrieljablonski
Copy link
Copy Markdown
Member

@gabrieljablonski gabrieljablonski commented Feb 18, 2026

Pull Request Template

Description

Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires.
Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality not to work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented on my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

This change is Reviewable

pranavrajs and others added 30 commits January 19, 2026 14:08
…oot#13278)

Fixes chatwoot#13097

### Problem
The PR chatwoot#12176 removed the `before_save :setup_webhooks` callback to fix
a race condition where Meta's webhook verification request arrived
before the channel was saved to the database. This change broke manual
WhatsApp Cloud channel setup. While embedded signup explicitly calls
`channel.setup_webhooks` in `EmbeddedSignupService`, manual setup had no
equivalent call - meaning the `subscribed_apps` endpoint was never
invoked and Meta never sent webhook events to Chatwoot.


### Solution
Added an `after_commit` callback that triggers webhook setup for manual
WhatsApp Cloud channels
… incoming messages (chatwoot#13319)

Fixes chatwoot#13317
Fixes an issue where WhatsApp attachment messages (images, audio, video,
documents) were failing to download. Messages were being created but
without attachments.

The `phone_number_id` parameter was being passed to the `GET
/<MEDIA_ID>` endpoint when downloading incoming media. According to
Meta's documentation:

> "Note that `phone_number_id` is optional. If included, the request
will only be processed if the business phone number ID included in the
query matches the ID of the business
  phone number **that the media was uploaded on**."

For incoming messages, media is uploaded by the customer, not by the
business phone number. Passing the business's `phone_number_id` causes
validation to fail with error: `Param phone_number_id is not a valid
whatsapp business phone number id ID`

This PR removes the `phone_number_id` parameter from the media URL
request for incoming messages.
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
Fixes signatures being generated on top of existing signature in draft
for improve, tone change and grammar
…13339)

- Fix ordered lists being sent as unordered lists in WhatsApp
integrations
- The WhatsApp markdown renderer was converting all lists to bullet
points (-), ignoring numbered list formatting
- Added ordered list support by tracking list_type and list_item_number
from CommonMarker AST metadata

Before:
Input: "1. First\n2. Second\n3. Third"
Output: "- First\n- Second\n- Third"

After:

Input: "1. First\n2. Second\n3. Third"
Output: "1. First\n2. Second\n3. Third"
…oot#13343)

Added Portuguese (Brazil) (`pt_BR`) to the CSAT template language
dropdown
…woot#12956)

- Use resolved contacts instead of accounts.contacts for search

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
…hatwoot#13276)

## Linear task:

https://linear.app/chatwoot/issue/CW-6318/searchkickimporterror-type-=-mapper-parsing-exception-reason-=-failed

## Description

Fixes Elasticsearch `mapper_parsing_exception` errors that occur when
`campaign_id` contain non-numeric string values

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- Unit tests
- use a local OpenSearch 3.4.0 cluster to verify actual indexing
behavior.


## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Removes `campaign_id` from the message search index payload to
simplify `additional_attributes`, keeping only `automation_rule_id`.
> 
> - `Messages::SearchDataPresenter#additional_attributes_data` now
returns only `automation_rule_id`
> - Specs updated to stop asserting `campaign_id` and continue
validating `automation_rule_id` and email subject handling
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5a9c8eb. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
- Improved design for the Chatwoot sign up page
… load (chatwoot#13355)

High-traffic accounts generate excessive database writes due to agents
frequently switching between conversations. The update_last_seen
endpoint was being called every time an agent loaded a conversation,
resulting in unnecessary updates to agent_last_seen_at and
assignee_last_seen_at even when there were no new messages to mark as
read.

#### Solution
Implemented throttling for the update_last_seen endpoint:

**Unread messages present:**
- Updates immediately without throttling to maintain accurate
read/unread state
- Uses assignee_unread_messages for assignees, unread_messages for other
agents

**No unread messages:**
- Throttles updates to once per hour per conversation
- Checks if agent_last_seen_at is older than 1 hour before updating
- For assignees, checks both agent_last_seen_at AND
assignee_last_seen_at - updates if either timestamp is old
- Skips DB write if all relevant timestamps were updated within the last
hour

- Consolidated two separate update_column calls into a single
update_columns call to reduce DB queries
# Pull Request Template

## Description
The Help Center sitemap endpoint (`/hc/:portal_slug/sitemap.xml`)
previously rendered a `<sitemapindex>` element while embedding article
URLs directly, which does not align with the sitemap specification.

This change fixes the structure by:
- Replacing `<sitemapindex>` with `<urlset>`
- Adding the required sitemap XML namespace
- Rendering each published article as a `<url>` entry with `<loc>` and
`<lastmod>`

This ensures the endpoint outputs a valid, self-contained sitemap
document.

Fixes chatwoot#13334

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?
- Updated the existing `portals_controller_spec.rb`
- Adjusted assertions to validate a `<urlset>` root element and the
sitemap XML namespace
- Verified that the sitemap returns only published article URLs
- Ran the updated RSpec controller specs locally


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
We are expanding Chatwoot’s automation capabilities by
introducing **Conversation Workflows**, a dedicated section in settings
where teams can configure rules that govern how conversations are closed
and what information agents must fill before resolving. This feature
helps teams enforce data consistency, collect structured resolution
information, and ensure downstream reporting is accurate.

Instead of having auto‑resolution buried inside Account Settings, we
introduced a new sidebar item:
- Auto‑resolve conversations (existing behaviour)
- Required attributes on resolution (new)

This groups all conversation‑closing logic into a single place.

#### Required Attributes on Resolve

Admins can now pick which custom conversation attributes must be filled
before an agent can resolve a conversation.

**How it works**

- Admin selects one or more attributes from the list of existing
conversation level custom attributes.
- These selected attributes become mandatory during resolution.
- List all the attributes configured via Required Attributes (Text,
Number, Link, Date, List, Checkbox)
- When an agent clicks Resolve Conversation:
If attributes already have values → the conversation resolves normally.
If attributes are missing → a modal appears prompting the agent to fill
them.

<img width="1554" height="1282" alt="CleanShot 2025-12-10 at 11 42
23@2x"
src="https://github.com/user-attachments/assets/4cd5d6e1-abe8-4999-accd-d4a08913b373"
/>


#### Custom Attributes Integration

On the Custom Attributes page, we will surfaced indicators showing how
each attribute is being used.

Each attribute will show badges such as:

- Resolution → used in the required‑on‑resolve workflow

- Pre‑chat form → already existing

<img width="2390" height="1822" alt="CleanShot 2025-12-10 at 11 43
42@2x"
src="https://github.com/user-attachments/assets/b92a6eb7-7f6c-40e6-bf23-6a5310f2d9c5"
/>


#### Admin Flow

- Navigate to Settings → Conversation Workflows.
- Under Required attributes on resolve, click Add Required Attribute.
- Pick from the dropdown list of conversation attributes.
- Save changes.

Agents will now be prompted automatically whenever they resolve.

<img width="2434" height="872" alt="CleanShot 2025-12-10 at 11 44 42@2x"
src="https://github.com/user-attachments/assets/632fc0e5-767c-4a1c-8cf4-ffe3d058d319"
/>



#### NOTES
- The Required Attributes on Resolve modal should only appear when
values are missing.
- Required attributes must block the resolution action until satisfied.
- Bulk‑resolve actions should follow the same rules — any conversation
missing attributes cannot be bulk‑resolved, rest will be resolved, show
a notification that the resolution cannot be done.
- API resolution does not respect the attributes.

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
## Summary
- Add `has_more` to contacts search API response to enable infinite
scroll without expensive count queries
- Set `count` to the number of items in the current page instead of
total count
- Implement "Load more" button for contacts search results
- Keep existing contacts visible while loading additional pages

## Changes

### Backend
- Add `fetch_contacts_with_has_more` method that fetches N+1 records to
determine if more pages exist
- Return `has_more` in search endpoint meta response
- Set `count` to current page size instead of total count

### Frontend
- Add `APPEND_CONTACTS` mutation for appending contacts without clearing
existing ones
- Update search action to support `append` parameter
- Add `ContactsLoadMore` component with loading state
- Update `ContactsListLayout` to support infinite scroll mode
- Update `ContactsIndex` to use infinite scroll for search view
…13152)

Fixes
https://linear.app/chatwoot/issue/CW-6358/handling-of-nil-conversation-in-the-readstatusservice
Improve handling of nil conversation in the `ReadStatusService` to
prevent potential errors. Ensure that the conversation is checked before
performing updates to message status. This change fixes the below error.



```
NoMethodError: undefined method 'conversations' for nil (NoMethodError)

    channel.inbox.contact_inboxes.find_by(source_id: tt_conversation_id).conversations.first
                                                                        ^^^^^^^^^^^^^^
    from app/services/tiktok/messaging_helpers.rb:29:in 'Tiktok::MessagingHelpers#find_conversation'
    from app/services/tiktok/read_status_service.rb:13:in 'Tiktok::ReadStatusService#conversation'
    from app/services/tiktok/read_status_service.rb:9:in 'Tiktok::ReadStatusService#perform'
    from app/jobs/webhooks/tiktok_events_job.rb:67:in 'Webhooks::TiktokEventsJob#im_mark_read_msg'
    from app/jobs/webhooks/tiktok_events_job.rb:31:in 'Webhooks::TiktokEventsJob#process_event'
    from app/jobs/webhooks/tiktok_events_job.rb:15:in 'block in Webhooks::TiktokEventsJob#perform'
    from app/jobs/mutex_application_job.rb:23:in 'MutexApplicationJob#with_lock'
    from app/jobs/webhooks/tiktok_events_job.rb:14:in 'Webhooks::TiktokEventsJob#perform'
    from activejob (7.1.5.2) lib/active_job/execution.rb:68:in 'block in ActiveJob::Execution#_perform_job'
    from activesupport (7.1.5.2) lib/active_support/callbacks.rb:121:in 'block in ActiveSupport::Callbacks#run_callbacks'
    from i18n (1.14.7) lib/i18n.rb:353:in 'I18n::Base#with_locale'
    from activejob (7.1.5.2) lib/active_job/translation.rb:9:in 'block (2 levels) in <module:Translation>'
    from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'BasicObject#instance_exec'
    from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'block in ActiveSupport::Callbacks#run_callbacks'
    from activesupport (7.1.5.2) lib/active_support/core_ext/time/zones.rb:65:in 'Time.use_zone'
    from activejob (7.1.5.2) lib/active_job/timezones.rb:9:in 'block (2 levels) in <module:Timezones>'
    from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'BasicObject#instance_exec'
    from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'block in ActiveSupport::Callbacks#run_callbacks'
    from activesupport (7.1.5.2) lib/active_support/callbacks.rb:141:in 'ActiveSupport::Callbacks#run_callbacks'
    from activejob (7.1.5.2) lib/active_job/execution.rb:67:in 'ActiveJob::Execution#_perform_job
```

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
)

Fixes
https://linear.app/chatwoot/issue/CW-6357/ensure-authentication-when-fetching-attachments
Update the attachment download method to include the access token in the
request headers, ensuring proper authentication when fetching
attachments.

https://business-api.tiktok.com/portal/docs?id=1832184455450626

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
## Linear issue


https://linear.app/chatwoot/issue/CW-6289/limit-the-number-of-notifications-per-user-to-300

## Description

Limits the number of notifications per user to 300 by introducing an
async trim job that runs after each notification creation. This prevents
unbounded notification growth that was causing DB CPU spikes.

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] This change requires a documentation update

## How Has This Been Tested?

- Added unit tests for TrimUserNotificationsJob

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Implements a dedicated purge job to control notification volume and
scheduling.
> 
> - Introduces `Notification::RemoveOldNotificationJob` (queue:
`purgable`) to delete notifications older than 1 month and trim each
user to the 300 most recent (deterministic by `created_at DESC, id
DESC`)
> - Adds daily cron (`remove_old_notification_job` at 22:30 UTC, queue
`purgable`) in `config/schedule.yml`
> - Removes ad-hoc triggering of the purge from
`TriggerScheduledItemsJob`
> - Adds/updates specs covering enqueue queue, old-notification
deletion, per-user trimming, and combined behavior
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9ea2b48. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
…ot#13356)

Fixes chatwoot#13354
Fixes
https://linear.app/chatwoot/issue/CW-6394/story-responses-are-not-being-shown-in-the-ui
When someone replies to your Instagram story, agents in Chatwoot only
see the reply text with no story image and no indication that it was a
story reply. This makes it impossible to understand what the customer is
responding to the message looks like a random text with no context. For
example, if a customer replies "Love this!" to your story, the agent
just sees "Love this!" with no way to know which story triggered the
conversation. This PR fixes the issue by storing the story attachment
and adding a context label.

<img width="1408" height="2052" alt="CleanShot 2026-01-27 at 19 19
38@2x"
src="https://github.com/user-attachments/assets/341afea9-98e3-4e47-b2fa-ef77fe32851f"
/>
## Description

This PR includes cron job to delete the orphans

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces a scheduled cleanup for conversations missing `contact` or
`inbox`.
> 
> - Adds `Internal::RemoveOrphanConversationsService` to batch-delete
orphan conversations (scoped by optional `account`, within a
configurable `days` window) with progress logging
> - New `Internal::RemoveOrphanConversationsJob` that invokes the
service; scheduled via `config/schedule.yml` to run every 12 hours on
`housekeeping` queue
> - Refactors rake task `chatwoot:ops:cleanup_orphan_conversations` to
use the service and report `total_deleted` after confirmation
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
59a2471. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Extracted hardcoded TikTok API version (`v1.3`) into a configurable
`TIKTOK_API_VERSION` setting, consistent with how Instagram and WhatsApp
handle API versions.

Fixes
https://linear.app/chatwoot/issue/CW-6408/tiktok-api-version-configurable-via-super-admin
The existing /api health check endpoint creates a new Redis connection
on every request and checks both Redis and Postgres availability. During
peak traffic, this creates unnecessary load and can cause cascading
failures when either service is slow - instances get marked unhealthy,
traffic shifts to remaining instances, which then also fail health
checks.

The new /health endpoint:
  - Returns immediately with 200 {"status":"woot"}
  - Skips all middleware and authentication
  - No Redis or Postgres dependency
- Suitable for health checks that only need to verify the web server is
responding
…#13347)

# Pull Request Template

## Description

This PR includes the following updates:

1. Updated the design system color tokens by introducing new tokens for
surfaces, overlays, buttons, labels, and cards, along with refinements
to existing shades.
2. Refreshed both light and dark themes with adjusted background,
border, and solid colors.
3. Replaced static Inter font files with the Inter variable font
(including italic), supporting weights from 100–900.
4. Added custom font weights (420, 440, 460, 520) along with custom
typography classes to enable more fine-grained and consistent typography
control.


## Type of change

- [x] New feature (non-breaking change which adds functionality)


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
sojan-official and others added 20 commits February 13, 2026 14:06
)

## Why
We observed `Webhooks::TwilioEventsJob` failures ending up in Sidekiq
dead jobs when Twilio callback payloads could not be mapped to a
`Channel::TwilioSms` record. In this scenario, channel lookup raised
`ActiveRecord::RecordNotFound`, which caused retries and eventual dead
jobs instead of a graceful drop.

Related Sentry issue/search:
-
https://chatwoot-p3.sentry.io/issues/?project=6382945&query=Webhooks%3A%3ATwilioEventsJob%20ActiveRecord%3A%3ARecordNotFound

## What changed
This PR keeps the existing lookup flow but makes it non-raising:
- `app/services/twilio/incoming_message_service.rb`
  - `find_by!` -> `find_by` for account SID + phone lookup
  - Added warning log when channel lookup misses
- `app/services/twilio/delivery_status_service.rb`
  - `find_by!` -> `find_by` for account SID + phone lookup
  - Added warning log when channel lookup misses

## Reproduction
Configure a Twilio webhook callback that reaches Chatwoot but does not
match an existing Twilio channel lookup path. Before this change, the
job raises `RecordNotFound` and can end up in dead jobs after retries.
After this change, the job logs the miss and exits safely.

## Testing
- `bundle exec rspec
spec/services/twilio/incoming_message_service_spec.rb
spec/services/twilio/delivery_status_service_spec.rb`
- `bundle exec rubocop app/services/twilio/incoming_message_service.rb
app/services/twilio/delivery_status_service.rb`
…t#13538)

## Summary
Fix hardcoded `Chatwoot` branding in two UI tooltips using the existing
`useBranding` flow so self-hosted/white-label deployments no longer show
the wrong brand text.

## Changes
- LabelSuggestion tooltip now uses:
  - `replaceInstallationName($t('LABEL_MGMT.SUGGESTIONS.POWERED_BY'))`
- Message avatar tooltip (native app/external echo) now uses:
  - `replaceInstallationName(t('CONVERSATION.NATIVE_APP_ADVISORY'))`

## Why
This follows the existing branding pattern already used in the product
and keeps behavior consistent across deployments.

## Notes
- No change to message logic or API behavior.
- `AGENTS.md` updated with a branding guidance note.

## Fixes
- Fixes chatwoot#13306
- Fixes chatwoot#13466

## Testing

<img width="195" height="155" alt="Screenshot 2026-02-13 at 3 55 39 PM"
src="https://github.com/user-attachments/assets/5b295cdd-6e5d-42c0-bbd7-23ba7052e1c3"
/>
<img width="721" height="152" alt="Screenshot 2026-02-13 at 3 55 48 PM"
src="https://github.com/user-attachments/assets/19cec2a0-451f-4fb3-bd61-7c2e591fc3c7"
/>
…oot#13353)

## Description

Fixes a critical bug where conversations assigned to a team could be
auto-assigned to agents outside that team when all team members were at
capacity.

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes core assignment selection for both legacy and v2 flows;
misconfiguration of `allow_auto_assign` or team membership could cause
conversations to remain unassigned.
> 
> **Overview**
> Prevents auto-assignment from crossing team boundaries by filtering
eligible agents to the conversation’s `team` members (and requiring
`team.allow_auto_assign`) in both the legacy `AutoAssignmentHandler`
path and the v2 `AutoAssignment::AssignmentService` (including the
Enterprise override).
> 
> Adds test coverage to ensure team-scoped conversations only assign to
team members, and are skipped when team auto-assign is disabled or no
team members are available; also updates the conversations controller
spec setup to include team membership.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
67ed2bd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…ed messages (chatwoot#13273)

## Description

Handle messages with null content properly in UI and email notifications

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## Relevant Screenshots:
<img width="688" height="765" alt="Screenshot 2026-01-21 at 4 43 00 PM"
src="https://github.com/user-attachments/assets/6a27c22e-2ae6-4377-a05d-cfa44bf181fe"
/>


## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches notification email templates and message rendering conditions;
mistakes could lead to missing content/attachments in emails or
incorrect UI visibility, but changes are localized and non-auth/security
related.
> 
> **Overview**
> Agent notification emails for *assigned* and *participating* new
messages now include the actual message details (sender name, rendered
text when present, and attachment links) and gracefully fall back when
content is unavailable.
> 
> To support this, the mailer now passes `@message` into Liquid via
`MessageDrop` (adding `attachments` URLs), and the dashboard message UI
now renders failed/external-error messages even when `content` is `null`
while tightening retry eligibility to require content or attachments
(and still within 1 day).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
475c8ce. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
…3487)

## Summary
This PR reduces duplicate failure noise for audio transcription jobs
that fail with permanent HTTP 400 responses, and fixes a file-format
edge case causing intermittent 400s.

Sentry issue: [CHATWOOT-99E /
6660541334](https://chatwoot-p3.sentry.io/issues/6660541334/)

## Confirmed root cause
For some attachments, the stored filename had no extension (example:
`speech`, content type `audio/mpeg`).
When the temporary transcription upload file was created without an
extension, OpenAI returned:
`Unrecognized file format` (HTTP 400).

## Scope of changes
1. `Messages::AudioTranscriptionJob`
- Keeps `discard_on Faraday::BadRequestError` to avoid retry storms on
permanent request errors.
- Adds explicit Rails warning logs for discarded jobs with
attachment/job/status context.

2. `Messages::AudioTranscriptionService`
- Keeps guaranteed temp file cleanup via `ensure`.
- Ensures temp upload files include an extension when the original
filename has none, derived from blob `content_type`.
- This addresses intermittent failures like extensionless `audio/mpeg`
files.

## Reproduction
Enable audio transcription for an account and process an audio
attachment whose stored filename has no extension (for example `speech`)
but valid audio content type (`audio/mpeg`).
Before this fix, OpenAI transcription could return HTTP 400
`Unrecognized file format` for that attachment while similar attachments
with extensions succeeded.

## Testing
Ran:
`bundle exec rubocop
enterprise/app/jobs/messages/audio_transcription_job.rb
enterprise/app/services/messages/audio_transcription_service.rb`

Result: both modified files pass lint with no offenses.
## Description

Adds missing analytics instrumentation for the editor AI funnel so we
can measure end-to-end usage and outcome quality.

### What was added

- Captain: Editor AI menu opened
- Captain: Generation failed
- Captain: AI-assisted message sent

### Behavior covered

- Tracks AI button click + menu open from both entry points:
    - top panel sparkle button
    - inline editor copilot button
- Tracks generation failures (initial + follow-up stages).
- Tracks whether accepted AI content was sent as-is or edited before
send.

### Notes

- Applies to editor Captain accept/send flow
(rewrite/summarize/reply_suggestion + follow-ups).
- Does not change Copilot sidebar flow instrumentation.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

### Manual verification steps

<img width="1906" height="832" alt="image"
src="https://github.com/user-attachments/assets/f0ade43b-aa8d-41be-8ca2-20a091a81f60"
/>

<img width="828" height="280" alt="image"
src="https://github.com/user-attachments/assets/be76219e-fb61-4a6e-bff5-dc085b0a3cc9"
/>

<img width="415" height="147" alt="image"
src="https://github.com/user-attachments/assets/36802c5c-33a7-49ed-bf7e-f0b02d86dccc"
/>

<img width="2040" height="516" alt="image"
src="https://github.com/user-attachments/assets/74b95288-bc86-4312-a282-14211ae8f25c"
/>


1. Open a conversation with Captain tasks enabled.
2. Click AI button in top panel and inline editor.
3. Confirm analytics events fire for:
    - AI menu opened
4. Run an AI action and force a failure scenario (or empty response
path) and confirm generation-failed event.
5. Accept AI output, then:
    - send without changes -> editedBeforeSend: false
    - edit then send -> editedBeforeSend: true

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
# Pull Request Template

## Description

Instruments captain v2

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.

Local testing:
<img width="864" height="510" alt="image"
src="https://github.com/user-attachments/assets/855ebce5-e8b8-4d22-b0bb-0d413769a6ab"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Langfuse logging improvements

## Description

Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes # (issue)
For reply suggestion: the errors are being stored inside output field,
but observations should be marked as errors.
For assistant: add credit_used metadata to filter handoffs from
ai-replies
For langfuse tool call: add `observation_type=tool`

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.

before:
<img width="1028" height="57" alt="image"
src="https://github.com/user-attachments/assets/70f6a36e-6c33-444c-a083-723c7c9e823a"
/>

after:
<img width="872" height="69" alt="image"
src="https://github.com/user-attachments/assets/1b6b6f5f-5384-4e9c-92ba-f56748fec6dd"
/>

`credit_used` to filter handoffs from AI replies that cause credit usage
<img width="1082" height="672" alt="image"
src="https://github.com/user-attachments/assets/90914227-553a-4c03-bc43-56b2018ac7c1"
/>


set `observation_type` to `tool`
<img width="726" height="1452" alt="image"
src="https://github.com/user-attachments/assets/e639cc9b-1c6c-4427-887e-23e5523bf64f"
/>


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
…3525)

# Pull Request Template

## Description

This PR adds support for removing labels from the conversation card
context menu. Assigned labels now show a checkmark, and clicking an
already-selected label will remove it.

Fixes
https://linear.app/chatwoot/issue/CW-6400/allow-removing-labels-directly-from-the-right-click-menu
chatwoot#13367
## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

**Screencast**


https://github.com/user-attachments/assets/4e3a6080-a67d-4851-9d10-d8dbf3ceeb04




## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
…woot#13523)

Some customers using WhatsApp inboxes with account-level webhooks were
reporting receiving duplicate `message_created` webhook deliveries for
every incoming message. Upon inspection, here's what we found

- Both payloads are identical.
- No errors appear in the application logs
- Webhook URL is only configured in one place. 

This meant, the system was sending the webhooks twice. For some context,
there's a know related issue... Meta's WhatsApp Business API can deliver
the same webhook notification multiple times for a single message. The
codebase already acknowledges this — there's a comment in
`IncomingMessageBaseService#process_messages` noting that "multiple
webhook events can be received against the same message due to
misconfigurations in the Meta business manager account." A deduplication
guard exists, but it doesn't actually work under concurrency.

### Rationale

The existing dedup was a three-step sequence: check Redis (`GET`), check
the database, then set a Redis flag (`SETEX`). Two Sidekiq workers
processing duplicate Meta webhooks simultaneously would both complete
the `GET` before either executed the `SETEX`, so both would proceed to
create a message. The `source_id` column has a non-unique index, so the
database wouldn't catch the duplicate either. Each message then
independently fires `after_create_commit`, dispatching two
`message_created` webhook events to the customer.

```
             Worker A                          Worker B
                │                                 │
                ▼                                 ▼
        Redis GET key ──► nil               Redis GET key ──► nil
                │                                 │
                │    ◄── both pass guard ──►      │
                │                                 │
                ▼                                 ▼
        Redis SETEX key                    Redis SETEX key
                │                                 │
                ▼                                 ▼
        BEGIN transaction               BEGIN transaction
        INSERT message                   INSERT message
        DELETE Redis key ◄─┐                      │
        COMMIT             │             DELETE Redis key
                           │             COMMIT
                           │                      │
                           └── key gone before ───┘
                              B's commit lands

                ▼                                 ▼
        after_create_commit              after_create_commit
        dispatch MESSAGE_CREATED         dispatch MESSAGE_CREATED
                │                                 │
                ▼                                 ▼
        WebhookJob ──► n8n               WebhookJob ──► n8n
                    (duplicate!)
```

There was a second, subtler problem visible in the diagram: the Redis
key was cleared *inside* the database transaction, before the
transaction committed. This opened a window where neither the Redis
check nor the database check would see the in-flight message.

The fix collapses the check-and-set into a single `SET NX EX` call,
which is atomic in Redis. The key is no longer eagerly cleared — it
expires naturally after 24 hours. The database lookup
(`find_message_by_source_id`) remains as a fallback for messages that
were created before the lock expired.

```
             Worker A                          Worker B
                │                                 │
                ▼                                 ▼
        Redis SET NX ──► OK              Redis SET NX ──► nil
                │                                 │
                ▼                                 ▼
        proceeds to create              returns early
        message normally                (lock already held)
```

### Implementation Notes

The lock logic is extracted into `Whatsapp::MessageDedupLock`, a small
class that wraps a single `Redis SET NX EX` call. This makes the
concurrency guarantee testable in isolation — the spec uses a
`CyclicBarrier` to race two threads against the same key and asserts
exactly one wins, without needing database writes,
`use_transactional_tests = false`, or monkey-patching.

Because the Redis lock now persists (instead of being cleared
mid-transaction), existing WhatsApp specs needed an `after` hook to
clean up `MESSAGE_SOURCE_KEY::*` keys between examples. Transactional
fixtures only roll back the database, not Redis.
# Pull Request Template

## Description

## Type of change

typo fix

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
…oot#13506)

# Pull Request Template

## Description

This PR includes:
1. Removes multiselect usage from the Merge Contact modal (Conversation
sidebar) and replaces it with the existing component used on the Contact
Details page.
2. Replaces legacy form and multiselect elements in Add and Edit
automations flows with next components.**(Also check Macros)**
3. Replace multiselect with ComboBox in contact form country field.
4. Replace multiselect with TagInput in create/edit attribute form.
5. Replace multiselect with TagInput for agent selection in inbox
creation.
6. Replace multiselect with ComboBox in Facebook channel page selection

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

**Screenshots**

1. **Merge modal**
<img width="741" height="449" alt="image"
src="https://github.com/user-attachments/assets/a05a96ec-0692-4d94-9e27-d3e85fd143e4"
/>
<img width="741" height="449" alt="image"
src="https://github.com/user-attachments/assets/fc1dc977-689d-4440-869d-2124e4ca9083"
/>

2. **Automations**
<img width="849" height="1089" alt="image"
src="https://github.com/user-attachments/assets/b0155f06-ab21-4f90-a2c8-5bfbd97b08f7"
/>
<img width="813" height="879" alt="image"
src="https://github.com/user-attachments/assets/0921ac4a-88f5-49ac-a776-cc02941b479c"
/>
<img width="849" height="826" alt="image"
src="https://github.com/user-attachments/assets/44358dae-a076-4e10-b7ba-a4e40ccd817f"
/>

3. **Country field**
<img width="462" height="483" alt="image"
src="https://github.com/user-attachments/assets/d5db9aa1-b859-4327-9960-957d7091678f"
/>

4. **Add/Edit attribute form**
<img width="619" height="646" alt="image"
src="https://github.com/user-attachments/assets/6ab2ea94-73e5-40b8-ac29-399c0543fa7b"
/>
<img width="619" height="646" alt="image"
src="https://github.com/user-attachments/assets/b4c5bb0e-baa0-4ef7-a6a2-adb0f0203243"
/>
<img width="635" height="731" alt="image"
src="https://github.com/user-attachments/assets/74890c80-b213-4567-bf5f-4789dda39d2d"
/>

5. **Agent selection in inbox creation**
<img width="635" height="534" alt="image"
src="https://github.com/user-attachments/assets/0003bad1-1a75-4f20-b014-587e1c19a620"
/>
<img width="809" height="602" alt="image"
src="https://github.com/user-attachments/assets/5e7ab635-7340-420a-a191-e6cd49c02704"
/>

7. **Facebook channel page selection**
<img width="597" height="444" alt="image"
src="https://github.com/user-attachments/assets/f7ec8d84-0a7d-4bc6-92a1-a1365178e319"
/>
<img width="597" height="444" alt="image"
src="https://github.com/user-attachments/assets/d0596c4d-94c1-4544-8b50-e7103ff207a6"
/>
<img width="597" height="444" alt="image"
src="https://github.com/user-attachments/assets/be097921-011b-4dbe-b5f1-5d1306e25349"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
…on (chatwoot#13549)

Fixes
https://linear.app/chatwoot/issue/CW-6494/add-shopify-mandatory-compliance-webhooks-for-app-store-listing

Shopify requires all public apps to handle three GDPR compliance
webhooks before they can be listed on the App Store. Their automated
review checks for these endpoints and verifies that apps validate HMAC
signatures on incoming requests. We were failing both checks.

This PR adds a single webhook endpoint at `POST /webhooks/shopify` that
receives all three compliance events. When Shopify sends a webhook, it
signs the payload with our app's client secret and includes the
signature in the `X-Shopify-Hmac-SHA256` header. Our controller reads
the raw body, computes the expected HMAC-SHA256 digest, and rejects
mismatched requests with a 401.

Shopify identifies the event type through the `X-Shopify-Topic` header.
For `customers/data_request` and `customers/redact`, we simply
acknowledge with a 200—Chatwoot doesn't persist any Shopify customer
data. All order lookups happen as live API calls at query time. For
`shop/redact`, which Shopify sends after a merchant uninstalls the app,
we delete the integration hook for that shop domain and remove the
stored access token and configuration.


### How to test via Rails console
```
secret = GlobalConfigService.load('SHOPIFY_CLIENT_SECRET', nil)
body = '{"shop_domain":"test.myshopify.com"}'
valid_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, body))
```

  #### Test 1: No HMAC → 401
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "401"
```
  ####  Test 2: Invalid HMAC → 401
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => 'invalid', 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "401"
```
  ####  Test 3: Valid HMAC, customers/data_request → 200
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "200"
```

####  Test 4: Valid HMAC, customers/redact → 200
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/redact' }
app.response.code  # => "200"
```

#### Test 5: Valid HMAC, shop/redact → 200 (deletes hook)
```  
# First check if a hook exists for this domain:
Integrations::Hook.where(app_id: 'shopify', reference_id: 'test.myshopify.com').count
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'shop/redact' }
app.response.code  # => "200"
```

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
# Pull Request Template

## Description

Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes:

The LLM call was wrapped in a transaction. This is an anti-pattern and
caused idle-connections which PG eventually terminated with
`PQconsumeInput() FATAL: terminating connection due to
idle-in-transaction timeout`

This resulted in activity messages being missing in some conversations
on captain handoff, failures queueing up for retry and captain
responding long after conversation was marked open/snoozed.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
locally and specs


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Bumps [rack](https://github.com/rack/rack) from 3.2.3 to 3.2.5.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rack/rack/blob/main/CHANGELOG.md">rack's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this file.
For info on how to format all future additions to this file please
reference <a href="https://keepachangelog.com/en/1.0.0/">Keep A
Changelog</a>.</p>
<h2>Unreleased</h2>
<h3>Security</h3>
<ul>
<li><a
href="https://github.com/advisories/GHSA-r657-rxjc-j557">CVE-2025-61780</a>
Improper handling of headers in <code>Rack::Sendfile</code> may allow
proxy bypass.</li>
<li><a
href="https://github.com/advisories/GHSA-6xw4-3v39-52mm">CVE-2025-61919</a>
Unbounded read in <code>Rack::Request</code> form parsing can lead to
memory exhaustion.</li>
<li><a
href="https://github.com/advisories/GHSA-whrj-4476-wvmp">CVE-2026-25500</a>
XSS injection via malicious filename in
<code>Rack::Directory</code>.</li>
<li><a
href="https://github.com/advisories/GHSA-mxw3-3hh2-x2mh">CVE-2026-22860</a>
Directory traversal via root prefix bypass in
<code>Rack::Directory</code>.</li>
</ul>
<h3>SPEC Changes</h3>
<ul>
<li>Define <code>rack.response_finished</code> callback arguments more
strictly. (<a
href="https://redirect.github.com/rack/rack/pull/2365">#2365</a>, <a
href="https://github.com/skipkayhil"><code>@​skipkayhil</code></a>)</li>
</ul>
<h3>Added</h3>
<ul>
<li>Add <code>Rack::Files#assign_headers</code> to allow overriding how
the configured file headers are set. (<a
href="https://redirect.github.com/rack/rack/pull/2377">#2377</a>, <a
href="https://github.com/codergeek121"><code>@​codergeek121</code></a>)</li>
<li>Add support for <code>rack.response_finished</code> to
<code>Rack::TempfileReaper</code>. (<a
href="https://redirect.github.com/rack/rack/pull/2363">#2363</a>, <a
href="https://github.com/skipkayhil"><code>@​skipkayhil</code></a>)</li>
<li>Add support for streaming bodies when using
<code>Rack::Events</code>. (<a
href="https://redirect.github.com/rack/rack/blob/main/redirect.github.com/rack/rack/pull/2375">#2375</a>,
<a href="https://github.com/unflxw"><code>@​unflxw</code></a>)</li>
<li>Add <code>deflaters</code> option to <code>Rack::Deflater</code> to
enable custom compression algorithms like zstd. (<a
href="https://redirect.github.com/rack/rack/issues/2168">#2168</a>, <a
href="https://github.com/alexanderadam"><code>@​alexanderadam</code></a>)</li>
<li>Add <code>Rack::Request#prefetch?</code> for identifying requests
with <code>Sec-Purpose: prefetch</code> header set. (<a
href="https://redirect.github.com/rack/rack/pull/2405">#2405</a>, <a
href="https://github.com/glaszig"><code>@​glaszig</code></a>)</li>
<li>Add <code>rack.request.trusted_proxy</code> environment key to
indicate whether the request is coming from a trusted proxy.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Raise before exceeding a part limit, not after. (<a
href="https://redirect.github.com/rack/rack/pull/2362">#2362</a>, <a
href="https://github.com/matthew-puku"><code>@​matthew-puku</code></a>)</li>
<li>Rack::Deflater now uses a fixed GZip mtime value. (<a
href="https://redirect.github.com/rack/rack/pull/2372">#2372</a>, <a
href="https://github.com/bensheldon"><code>@​bensheldon</code></a>)</li>
<li>Multipart parser drops support for RFC 2231 <code>filename*</code>
parameter (prohibited by RFC 7578) and now properly handles UTF-8
encoded filenames via percent-encoding and direct UTF-8 bytes. (<a
href="https://redirect.github.com/rack/rack/pull/2398">#2398</a>, <a
href="https://github.com/wtn"><code>@​wtn</code></a>)</li>
<li>The query parser now raises
<code>Rack::QueryParser::IncompatibleEncodingError</code> if we try to
parse params that are not ASCII compatible. (<a
href="https://redirect.github.com/rack/rack/pull/2416">#2416</a>, <a
href="https://github.com/bquorning"><code>@​bquorning</code></a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Multipart parser: limit MIME header size check to the unread buffer
region to avoid false <code>multipart mime part header too large</code>
errors when previously read data accumulates in the scan buffer. (<a
href="https://redirect.github.com/rack/rack/pull/2392">#2392</a>, <a
href="https://github.com/alpaca-tc"><code>@​alpaca-tc</code></a>, <a
href="https://github.com/willnet"><code>@​willnet</code></a>, <a
href="https://github.com/krororo"><code>@​krororo</code></a>)</li>
<li>Fix <code>Rack::MockResponse#body</code> when the body is a Proc.
(<a href="https://redirect.github.com/rack/rack/pull/2420">#2420</a>, <a
href="https://redirect.github.com/rack/rack/pull/2423">#2423</a>, <a
href="https://github.com/tavianator"><code>@​tavianator</code></a>, [<a
href="https://github.com/ioquatix"><code>@​ioquatix</code></a>])</li>
</ul>
<h2>[3.2.4] - 2025-11-03</h2>
<h3>Fixed</h3>
<ul>
<li>Multipart parser: limit MIME header size check to the unread buffer
region to avoid false <code>multipart mime part header too large</code>
errors when previously read data accumulates in the scan buffer. (<a
href="https://redirect.github.com/rack/rack/pull/2392">#2392</a>, <a
href="https://github.com/alpaca-tc"><code>@​alpaca-tc</code></a>, <a
href="https://github.com/willnet"><code>@​willnet</code></a>, <a
href="https://github.com/krororo"><code>@​krororo</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rack/rack/commit/bb5f3555bd12b9065112353e829298b3b5623ceb"><code>bb5f355</code></a>
Bump patch version.</li>
<li><a
href="https://github.com/rack/rack/commit/f9bde3bc2dde2771185ac1a7b7602a4d9fa0a0d8"><code>f9bde3b</code></a>
Prevent directory traversal via root prefix bypass.</li>
<li><a
href="https://github.com/rack/rack/commit/93a68f58aa82aa48f09b751501f19f5e760dd406"><code>93a68f5</code></a>
XSS injection via malicious filename in
<code>Rack::Directory</code>.</li>
<li><a
href="https://github.com/rack/rack/commit/3b8b0d22d68a7fb30fdea40f838d0f95a05c134d"><code>3b8b0d2</code></a>
Fix MockResponse#body when the body is a Proc (<a
href="https://redirect.github.com/rack/rack/issues/2420">#2420</a>)</li>
<li><a
href="https://github.com/rack/rack/commit/4c24539777db8833d78f881680cd245878cfba31"><code>4c24539</code></a>
Bump patch version.</li>
<li><a
href="https://github.com/rack/rack/commit/3ba5e4f22f55abac21037bb137e56e5c8e36b673"><code>3ba5e4f</code></a>
Allow Multipart head to span read boundary. (<a
href="https://redirect.github.com/rack/rack/issues/2392">#2392</a>)</li>
<li>See full diff in <a
href="https://github.com/rack/rack/compare/v3.2.3...v3.2.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rack&package-manager=bundler&previous-version=3.2.3&new-version=3.2.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 18, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/merge-upstream-4.11.0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Merges upstream 4.11.0, introducing new reporting endpoints/helpers, conversation workflow settings, Captain-related UI changes, contact list pagination enhancements, and broad UI theming updates.

Changes:

  • Updates Vuex conversation/contact modules (conversationId-scoped mutations, contact append + has_more).
  • Adds new reports builders/endpoints and settings routes (conversation workflow, outgoing message counts, matrix + distribution reports).
  • Migrates/refreshes UI components and styling (surface tokens, ComboBox usage, sidebar improvements, Captain UX).

Reviewed changes

Copilot reviewed 286 out of 503 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
app/javascript/dashboard/store/modules/labels.js Adjusts label getter to return a default object when not found
app/javascript/dashboard/store/modules/conversations/index.js Makes several mutations conversationId-scoped and adds SET_CHAT_DATA_FETCHED
app/javascript/dashboard/store/modules/conversations/actions.js Updates commits/dispatches to pass conversationId and adds custom-attributes update on toggle
app/javascript/dashboard/store/modules/contacts/mutations.js Adds hasMore meta handling and APPEND_CONTACTS mutation
app/javascript/dashboard/store/modules/contacts/index.js Adds meta.hasMore to contacts state
app/javascript/dashboard/store/modules/contacts/actions.js Adds append option to search and uses append mutation
app/javascript/dashboard/routes/dashboard/settings/settings.routes.js Adds conversation workflow settings routes
app/javascript/dashboard/routes/dashboard/settings/reports/helpers/reportFilterHelper.js Adds helpers to parse/generate report/filter URL params
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersTeams.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersRatings.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersLabels.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersInboxes.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersDateRange.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersDateGroupBy.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersAgents.spec.js Removes legacy report filter spec
app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportsWrapper.vue Updates wrapper background token
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/v3/ActiveFilterChip.vue Makes id nullable and adds showClearFilter prop passthrough
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Teams.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Ratings.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Labels.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Inboxes.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/DateRange.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/DateGroupBy.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Agents.vue Removes legacy filter component
app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue Adds spacing styles to report container
app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue Replaces ReportFilterSelector with ReportFilters
app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue Dispatches agents fetch on mount
app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue Replaces ReportFilterSelector with ReportFilters
app/javascript/dashboard/routes/dashboard/settings/profile/Wrapper.vue Updates settings background token
app/javascript/dashboard/routes/dashboard/settings/macros/MacroNodes.vue Updates text color token
app/javascript/dashboard/routes/dashboard/settings/inbox/components/BusinessDay.vue Updates text color token
app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue Replaces multiselect with ComboBox for page selection
app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue Adjusts tabs wrapper positioning classes
app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/index.vue Adds Conversation Workflow settings page
app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/conversationWorkflow.routes.js Adds Conversation Workflow route config
app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue Updates link color token
app/javascript/dashboard/routes/dashboard/settings/automation/constants.js Adds pending conversation automation action
app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue Migrates add/edit modals to dialog refs and reduces state flags
app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityEditPage.vue Adds async error handling + alerts around CRUD operations
app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentCreatePage.vue Adds inboxId query support for breadcrumb/navigation + forwards query
app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/assignmentPolicy.routes.js Switches feature flag to ADVANCED_ASSIGNMENT
app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue Feature-gates Agent Capacity card by account flags
app/javascript/dashboard/routes/dashboard/settings/account/components/AutoResolve.vue Replaces SectionLayout structure with inline layout
app/javascript/dashboard/routes/dashboard/settings/account/Index.vue Removes AutoResolve config from account settings index
app/javascript/dashboard/routes/dashboard/settings/Wrapper.vue Updates wrapper background token
app/javascript/dashboard/routes/dashboard/settings/SettingsWrapper.vue Updates settings wrapper background token
app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue Updates header background token
app/javascript/dashboard/routes/dashboard/inbox/helpers/InboxViewHelpers.js Updates notification icon color token
app/javascript/dashboard/routes/dashboard/inbox/components/MenuItem.vue Updates hover text color token
app/javascript/dashboard/routes/dashboard/inbox/components/InboxItemHeader.vue Adds background token to header container
app/javascript/dashboard/routes/dashboard/inbox/components/InboxDisplayMenu.vue Updates active state color token
app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue Updates inbox panel background token and selection rounding
app/javascript/dashboard/routes/dashboard/inbox/InboxEmptyState.vue Updates empty state background token
app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsIndexPage.vue Updates loading background token
app/javascript/dashboard/routes/dashboard/helpcenter/pages/HelpCenterPageRouteView.vue Updates page background token
app/javascript/dashboard/routes/dashboard/helpcenter/components/UpgradePage.vue Updates upgrade page background token
app/javascript/dashboard/routes/dashboard/conversation/customAttributes/CustomAttributes.vue Adjusts attribute presence check + updates zebra background tokens
app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue Migrates merge modal to ref-based open
app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue Replaces multiselect country selector with ComboBox and adds TikTok profile field
app/javascript/dashboard/routes/dashboard/conversation/ConversationParticipant.vue Removes explicit background class
app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue Updates assignee dispatch payload and removes explicit background class
app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue Updates page background token
app/javascript/dashboard/routes/dashboard/captain/pages/CaptainPageRouteView.vue Updates page background token
app/javascript/dashboard/routes/dashboard/captain/pages/AssistantsIndexPage.vue Updates loading background token
app/javascript/dashboard/routes/dashboard/captain/assistants/inboxes/Index.vue Refetches inboxes when assistantId changes (watch)
app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue Updates page background token
app/javascript/dashboard/routes/dashboard/Dashboard.vue Adds main background token
app/javascript/dashboard/modules/search/components/SearchView.vue Updates page background token
app/javascript/dashboard/modules/search/components/SearchInput.vue Updates focus color token
app/javascript/dashboard/modules/contact/components/ContactDropdownItem.vue Removes legacy dropdown item component
app/javascript/dashboard/i18n/locale/en/signup.json Adds new signup CTA i18n key
app/javascript/dashboard/i18n/locale/en/report.json Adds filter placeholders and copy tweaks
app/javascript/dashboard/i18n/locale/en/integrations.json Expands Captain/OpenAI option labels and reply options
app/javascript/dashboard/i18n/locale/en/inboxMgmt.json Adds assignment policy UI copy
app/javascript/dashboard/i18n/locale/en/general.json Adds generic buttons + choice toggle labels
app/javascript/dashboard/i18n/locale/en/datePicker.json Adds new date picker labels/options
app/javascript/dashboard/i18n/locale/en/conversation.json Adds unsupported TikTok message, label removal messages, restricted messaging, copilot copy
app/javascript/dashboard/i18n/locale/en/contact.json Adds “Load more” label
app/javascript/dashboard/i18n/locale/en/bulkActions.json Adds resolve-required-attributes bulk action copy
app/javascript/dashboard/i18n/locale/en/automation.json Adds pending conversation automation label
app/javascript/dashboard/i18n/locale/en/attributesMgmt.json Adds badges labels
app/javascript/dashboard/helper/validations.js Allows pending_conversation action
app/javascript/dashboard/helper/specs/editorHelper.spec.js Removes channel-specific formatting tests
app/javascript/dashboard/helper/editorHelper.js Adds showCaptain flag and hides copilot menu by default
app/javascript/dashboard/helper/automationHelper.js Returns cloned defaults to avoid shared state mutation
app/javascript/dashboard/helper/AnalyticsHelper/events.js Introduces CAPTAIN_EVENTS and replaces legacy OpenAI events usage
app/javascript/dashboard/featureFlags.js Adds advanced assignment, captain tasks, required attributes flags
app/javascript/dashboard/constants/globals.js Updates testimonial URL
app/javascript/dashboard/constants/editor.js Adds copilot menu key to multiple contexts
app/javascript/dashboard/composables/utils/useKbd.js Improves Mac detection, updates enter glyph, exports getModifierKey
app/javascript/dashboard/composables/useLabelSuggestions.js Adds label suggestion composable using Captain tasks API
app/javascript/dashboard/composables/useConversationRequiredAttributes.js Adds required-attributes workflow composable
app/javascript/dashboard/composables/useAutomation.js Makes automation types reactive and dedupes injected custom-attr conditions
app/javascript/dashboard/composables/commands/useConversationHotKeys.js Switches AI gating to Captain tasks and changes rewrite action keys/labels
app/javascript/dashboard/composables/commands/spec/useConversationHotKeys.spec.js Updates tests to mock useCaptain instead of useAI
app/javascript/dashboard/composables/captain/constants.js Adds Captain error types/constants
app/javascript/dashboard/components/widgets/conversation/linear/CreateOrLinkIssue.vue Adjusts tabs height
app/javascript/dashboard/components/widgets/conversation/copilot/CaptainLoader.vue Adds new Captain loader animation component
app/javascript/dashboard/components/widgets/conversation/conversation/LabelSuggestion.vue Switches integration gating to Captain tasks + branding replacement + events
app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItem.vue Adds check icon for already-assigned labels
app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue Adds label removal support in menu
app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/languages.js Adds Portuguese (Brazil) locale option
app/javascript/dashboard/components/widgets/conversation/ReplyBoxBanner.vue Updates assignee dispatch payload
app/javascript/dashboard/components/widgets/conversation/OnboardingFeatureCard.vue Updates card background token
app/javascript/dashboard/components/widgets/conversation/MessageSignatureMissingAlert.vue Removes horizontal margin class
app/javascript/dashboard/components/widgets/conversation/EmptyState/EmptyState.vue Adds background token to empty state
app/javascript/dashboard/components/widgets/conversation/CopilotEditorSection.vue Adds copilot editor section w/ loading state
app/javascript/dashboard/components/widgets/conversation/ConversationSidebar.vue Updates sidebar background token
app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue Removes border/background classes and adjusts padding
app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue Adds label removal emit + context menu label state
app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue Updates background and tabs styling
app/javascript/dashboard/components/widgets/WootWriter/EditorModeToggle.vue Forces private-note mode when replies are restricted
app/javascript/dashboard/components/widgets/WootWriter/CopilotReplyBottomPanel.vue Adds bottom panel for accepting/discarding copilot content
app/javascript/dashboard/components/widgets/ChatTypeTabs.vue Adjusts tabs height
app/javascript/dashboard/components/widgets/AutomationFileInput.vue Tweaks input styling
app/javascript/dashboard/components/widgets/AutomationActionTeamMessageInput.vue Migrates team selector to new MultiSelect and cleans styling
app/javascript/dashboard/components/widgets/AttachmentsPreview.vue Improves attachment list layout wrapping
app/javascript/dashboard/components/widgets/AIAssistanceCTAButton.vue Removes legacy AI assistance CTA button
app/javascript/dashboard/components/ui/Tabs/TabsItem.vue Restyles tabs and badge appearance
app/javascript/dashboard/components/ui/Dropdown/DropdownSearch.vue Uses type="search" and outline override
app/javascript/dashboard/components/ui/DatePicker/components/CalendarYear.vue Stops click propagation on year select
app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeek.vue Updates “today” outline text color token
app/javascript/dashboard/components/ui/DatePicker/components/CalendarMonth.vue Stops click propagation on month select
app/javascript/dashboard/components/ui/DatePicker/components/CalendarFooter.vue Removes ghost prop on apply button
app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateRange.vue Adjusts typography and adds separator rendering
app/javascript/dashboard/components/ui/DatePicker/components/CalendarAction.vue Stops click propagation on navigation
app/javascript/dashboard/components/copilot/CopilotContainer.vue Updates copilot panel background token
app/javascript/dashboard/components/ConversationItem.vue Adds removeLabels wiring
app/javascript/dashboard/components/ChatListHeader.vue Adjusts header height
app/javascript/dashboard/components/Accordion/AccordionItem.vue Updates icon text color and removes background on body
app/javascript/dashboard/components-next/textarea/TextArea.vue Emits focus/blur events in addition to model updates
app/javascript/dashboard/components-next/taginput/helper/tagInputHelper.js Adds allowCreate and skipLabelDedup options
app/javascript/dashboard/components-next/taginput/helper/spec/tagInputHelper.spec.js Adds tests for new tag menu behavior flags
app/javascript/dashboard/components-next/tabbar/TabBar.vue Updates active text color token
app/javascript/dashboard/components-next/switch/Switch.vue Tweaks animation easing
app/javascript/dashboard/components-next/sidebar/provider.js Adds sidebar resize + shared popover state management
app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue Adds min-width constraints and adjusts gradient token
app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue Adds collapsed rendering mode
app/javascript/dashboard/components-next/sidebar/SidebarGroupLeaf.vue Adjusts spacing and active styling
app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue Restyles group header and badge
app/javascript/dashboard/components-next/sidebar/SidebarChangelogCard.vue Exposes loading/data and forwards attrs to support popover button
app/javascript/dashboard/components-next/sidebar/SidebarChangelogButton.vue Adds changelog popover trigger button
app/javascript/dashboard/components-next/sidebar/SidebarAccountSwitcher.vue Adds collapsed logo trigger and adjusts dropdown rendering
app/javascript/dashboard/components-next/sidebar/MobileSidebarLauncher.vue Tweaks transition easing
app/javascript/dashboard/components-next/sidebar/ChannelLeaf.vue Removes active background usage in icon container
app/javascript/dashboard/components-next/pagination/PaginationFooter.vue Updates backdrop gradient token
app/javascript/dashboard/components-next/message/constants.js Adds IG_STORY_REPLY attachment type
app/javascript/dashboard/components-next/message/bubbles/Unsupported.vue Shows channel-specific “unsupported” messages (incl. TikTok)
app/javascript/dashboard/components-next/message/bubbles/Template/CallToAction.vue Updates button text color token
app/javascript/dashboard/components-next/message/bubbles/Template/CSAT.vue Updates button text color token
app/javascript/dashboard/components-next/message/bubbles/InstagramStory.vue Adds story-reply detection and label
app/javascript/dashboard/components-next/message/bubbles/Base.vue Formats and sanitizes reply preview content
app/javascript/dashboard/components-next/message/MessageList.vue Updates list background token
app/javascript/dashboard/components-next/message/MessageError.vue Disables retry when there’s no content/attachments or after 1 day
app/javascript/dashboard/components-next/input/ChoiceToggle.vue Adds new boolean choice toggle input component
app/javascript/dashboard/components-next/filter/operators.js Updates operator icon color token
app/javascript/dashboard/components-next/filter/inputs/SingleSelect.vue Adds dropdown height, deselect disable, and selected null guard
app/javascript/dashboard/components-next/filter/inputs/MultiSelect.vue Adds dropdown height prop support
app/javascript/dashboard/components-next/filter/inputs/FilterSelect.vue Guards missing options and supports disabled “header” rows
app/javascript/dashboard/components-next/filter/ConditionRow.vue Adds null-safe operator lookup and exposes resetValidation
app/javascript/dashboard/components-next/dropdown-menu/base/DropdownSection.vue Adds configurable height prop
app/javascript/dashboard/components-next/dialog/Dialog.vue Adds position option and prevents slot rendering when closed
app/javascript/dashboard/components-next/combobox/ComboBox.vue Allows deselect by selecting the same option
app/javascript/dashboard/components-next/changelog-card/StackedChangelogCard.vue Updates card background token
app/javascript/dashboard/components-next/captain/pageComponents/response/ResponseForm.vue Updates button text color token
app/javascript/dashboard/components-next/captain/pageComponents/inbox/ConnectInboxForm.vue Updates button text color token
app/javascript/dashboard/components-next/captain/pageComponents/document/RelatedResponses.vue Adds total count to dialog title
app/javascript/dashboard/components-next/captain/pageComponents/document/DocumentForm.vue Updates button text color token
app/javascript/dashboard/components-next/captain/pageComponents/customTool/CustomToolForm.vue Updates button text color token
app/javascript/dashboard/components-next/captain/pageComponents/assistant/AssistantForm.vue Updates button text color token
app/javascript/dashboard/components-next/captain/assistant/AddNewScenariosDialog.vue Updates button text color token
app/javascript/dashboard/components-next/captain/PageLayout.vue Updates page background token
app/javascript/dashboard/components-next/button/Button.vue Updates blue color tokens and slate solid background token
app/javascript/dashboard/components-next/NewConversation/components/EmailOptions.vue Enables tag input create for CC/BCC
app/javascript/dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue Refactors to accept external formState
app/javascript/dashboard/components-next/Inbox/InboxCard.vue Updates notification color fallback token
app/javascript/dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryForm.vue Updates button text color token
app/javascript/dashboard/components-next/HelpCenter/LocaleCard/LocaleCard.vue Updates “default” chip text color token
app/javascript/dashboard/components-next/HelpCenter/HelpCenterLayout.vue Updates page background token
app/javascript/dashboard/components-next/HelpCenter/CategoryCard/CategoryCard.vue Updates hover link color token
app/javascript/dashboard/components-next/HelpCenter/ArticleCard/ArticleCard.vue Updates hover link color token
app/javascript/dashboard/components-next/EmptyStateLayout.vue Updates backdrop gradient and typography classes
app/javascript/dashboard/components-next/CustomAttributes/OtherAttribute.vue Updates link color token
app/javascript/dashboard/components-next/ConversationWorkflow/constants.js Adds attribute type constants
app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributeItem.vue Adds required attribute item display component
app/javascript/dashboard/components-next/Conversation/ConversationRequiredEmpty.vue Adds empty state for required attributes
app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue Updates button text color token
app/javascript/dashboard/components-next/Contacts/ContactsLoadMore.vue Adds “Load more” component for contacts infinite mode
app/javascript/dashboard/components-next/Contacts/ContactsListLayout.vue Adds infinite mode footer and loadMore event
app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue Simplifies create-contact dialog open logic
app/javascript/dashboard/components-next/Contacts/ContactsForm/ContactImportDialog.vue Updates link color token
app/javascript/dashboard/components-next/Contacts/ContactsDetailsLayout.vue Updates page background token
app/javascript/dashboard/components-next/Companies/CompaniesListLayout.vue Updates page background token
app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue Updates button text color token
app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue Updates button text color token
app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignForm.vue Updates button text color token
app/javascript/dashboard/components-next/Campaigns/CampaignLayout.vue Updates page background token
app/javascript/dashboard/components-next/AssignmentPolicy/components/story/BaseInfo.story.vue Removes enabled/status props from story
app/javascript/dashboard/components-next/AssignmentPolicy/components/RadioCard.vue Adds disabled state and premium badge UI
app/javascript/dashboard/components-next/AssignmentPolicy/components/FairDistribution.vue Fixes duration unit conversion (seconds↔minutes)
app/javascript/dashboard/components-next/AssignmentPolicy/components/ExclusionRules.vue Updates button text color token
app/javascript/dashboard/components-next/AssignmentPolicy/components/DataTable.vue Adds navigate event and row navigation UX
app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.vue Removes enabled badge from card header
app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.story.vue Removes enabled from story props
app/javascript/dashboard/assets/scss/plugins/_multiselect.scss Updates multiselect caret color token
app/javascript/dashboard/api/integrations/openapi.js Removes old OpenAI API client
app/javascript/dashboard/App.vue Adds app background class
app/helpers/super_admin/features.yml Moves captain feature definition and sets enabled true
app/controllers/webhooks/shopify_controller.rb Adds Shopify webhook controller with HMAC verification
app/controllers/super_admin/app_configs_controller.rb Adds allowed config keys incl. Captain + widget expiry and TikTok API version
app/controllers/health_controller.rb Adds a lightweight health endpoint controller
app/controllers/devise_overrides/passwords_controller.rb Prevents user enumeration by returning 200 for reset requests
app/controllers/api/v2/accounts/reports_controller.rb Adds inbox-label matrix, first-response distribution, outgoing messages count endpoints
app/controllers/api/v1/widget/conversations_controller.rb Adds transcript feature gating + rate limiting and increments sent count
app/controllers/api/v1/accounts_controller.rb Refactors settings params and prepends module hook
app/controllers/api/v1/accounts/conversations_controller.rb Adds transcript gating/rate limiting and throttles last_seen updates
app/controllers/api/v1/accounts/contacts_controller.rb Changes search scope and adds has_more fetching mode
app/builders/v2/reports/outgoing_messages_count_builder.rb Implements outgoing message counts grouped by agent/team/inbox/label
app/builders/v2/reports/inbox_label_matrix_builder.rb Implements inbox × label matrix report builder
app/builders/v2/reports/first_response_time_distribution_builder.rb Implements first response time histogram by channel type
app/builders/messages/instagram/base_message_builder.rb Adds IG story reply attachment creation and outgoing echo semantics
VERSION_CW Bumps version to 4.11.0
Gemfile Updates ai/llm deps and adds cld3
AGENTS.md Adds seed instructions, worktree workflow, and branding note
.env.example Adds REDIS_VELMA_SIZE example
.circleci/config.yml Increases backend test parallelism

from: from ? Number(from) : null,
to: to ? Number(to) : null,
businessHours: business_hours === 'true',
groupBy: group_by ? Number(group_by) : null,
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coercing group_by to Number will yield NaN if the URL param is a string enum (e.g. 'day', 'week', 'month'), which is a common pattern for report grouping. Keep groupBy as a string (or only coerce if it’s numeric) to avoid propagating NaN into the report query/state.

Suggested change
groupBy: group_by ? Number(group_by) : null,
groupBy: group_by || null,

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +7
export const generateReportURLParams = ({
from,
to,
businessHours,
groupBy,
range,
}) => {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new helper introduces URL param logic but there are no accompanying specs in the diff. Add unit tests covering generation/parsing (dates, businessHours boolean roundtrip, range, groupBy, and filter params) to prevent regressions as report filters evolve.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 10 to +17
"BETA_DESCRIPTION": "This feature is in beta and may change as we improve it.",
"ACCEPT": "Accept",
"DISCARD": "Discard",
"PREFERRED": "Preferred"
},
"CHOICE_TOGGLE": {
"YES": "Yes",
"NO": "No"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New user-facing English strings were added (e.g., GENERAL.ACCEPT, GENERAL.DISCARD, CHOICE_TOGGLE.YES/NO, and many others in en/*). Per project guidelines, please add corresponding pt-BR translations for all newly introduced keys to keep locales in sync.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +3 to +12
function isMacOS() {
// Check modern userAgentData API first
if (navigator.userAgentData?.platform) {
return navigator.userAgentData.platform === 'macOS';
}
// Fallback to navigator.platform
return (
navigator.platform.startsWith('Mac') || navigator.platform === 'iPhone'
);
}
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module assumes navigator is always defined at import/runtime. In SSR, pre-rendering, or certain test environments, referencing navigator can throw a ReferenceError. Guard with typeof navigator !== 'undefined' (and default to non-mac behavior) to keep the composable safe outside the browser.

Copilot uses AI. Check for mistakes.
return head :unauthorized if hmac_header.blank?

computed = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, data))
return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(computed, hmac_header)
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActiveSupport::SecurityUtils.secure_compare raises if the two strings have different lengths. A malformed/missing/incorrect X-Shopify-Hmac-SHA256 header could trigger an exception and turn this endpoint into a 500. Add a length check (or rescue ArgumentError) and treat mismatches as :unauthorized.

Suggested change
return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(computed, hmac_header)
return head :unauthorized unless computed.bytesize == hmac_header.bytesize &&
ActiveSupport::SecurityUtils.secure_compare(computed, hmac_header)

Copilot uses AI. Check for mistakes.
# High-traffic accounts generate excessive DB writes when agents frequently switch between conversations.
# Throttle last_seen updates to once per hour when there are no unread messages to reduce DB load.
# Always update immediately if there are unread messages to maintain accurate read/unread state.
return update_last_seen_on_conversation(DateTime.now.utc, true) if assignee? && @conversation.assignee_unread_messages.any?
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early return when the assignee has unread messages skips dispatch_messages_read_event, which likely regresses real-time read/unread updates for agents. Consider dispatching the read event before returning (or moving it into update_last_seen_on_conversation when update_assignee is true).

Suggested change
return update_last_seen_on_conversation(DateTime.now.utc, true) if assignee? && @conversation.assignee_unread_messages.any?
if assignee? && @conversation.assignee_unread_messages.any?
dispatch_messages_read_event
return update_last_seen_on_conversation(DateTime.now.utc, true)
end

Copilot uses AI. Check for mistakes.
# No unread messages - apply throttling to limit DB writes
return unless should_update_last_seen?

dispatch_messages_read_event if assignee?
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early return when the assignee has unread messages skips dispatch_messages_read_event, which likely regresses real-time read/unread updates for agents. Consider dispatching the read event before returning (or moving it into update_last_seen_on_conversation when update_assignee is true).

Copilot uses AI. Check for mistakes.

@has_more = results.size > RESULTS_PER_PAGE
results = results.first(RESULTS_PER_PAGE) if @has_more
@contacts_count = results.size
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting @contacts_count to the page size changes the meaning of meta.count (previously total count via total_count). If the frontend still uses count for totals, this will display incorrect counts and can break pagination logic. If the goal is to avoid expensive COUNT queries, consider returning both returned_count and has_more (or rename the field), or keep count as total when pagination UI relies on it.

Suggested change
@contacts_count = results.size
@returned_contacts_count = results.size

Copilot uses AI. Check for mistakes.
@gabrieljablonski gabrieljablonski merged commit fdde54f into main Feb 18, 2026
20 checks passed
@gabrieljablonski gabrieljablonski deleted the chore/merge-upstream-4.11.0 branch February 18, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.